#define USE_VOLATILE

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Microsoft.Xna.Framework;
using System.Collections.ObjectModel;

namespace Oops.Xna.Framework
{
	/// <summary>
	/// Defines the callback contract for asynchronous game component updates.
	/// </summary>
	public interface IAsyncUpdateCallback
	{
		/// <summary>
		/// Occurs when the update process begins.
		/// </summary>
		void OnUpdateStarted(AsyncGameComponent.AsyncGameTime gameTime);

		/// <summary>
		/// Occurs when the update process completes.
		/// </summary>
		void OnUpdateCompleted(AsyncGameComponent.AsyncGameTime gameTime);
	}

	/// <summary>
	/// Allows for asynchronous updating on game components.
	/// </summary>
	public interface IAsyncUpdateable : IUpdateable 
	{
		/// <summary>
		/// Gets the asynchronouse state of the object.
		/// </summary>
		bool AsyncEnabled { get; }

		/// <summary>
		/// Raised when the <see cref="IAsyncUpdateable.AsyncEnabled"/> property changes. 
		/// </summary>
		event EventHandler AsyncEnabledChanged;

		/// <summary>
		/// Starts an asynchronous update
		/// </summary>
		/// <param name="gameTime">Snapshot of the game's timing state.</param>
		void BeginUpdate(Microsoft.Xna.Framework.GameTime gameTime);

		/// <summary>
		/// Ends an asynchronous update.
		/// </summary>
		void EndUpdate();
 	}

	/// <summary>
	/// A game component that enables asynchronous updates.
	/// </summary>
	public class AsyncGameComponent : DrawableGameComponent, IAsyncUpdateable
	{
		#region AsyncGameTime Declaration

		/// <summary>
		/// Snapshot of the game timing state expressed in values that can be used by
		//  variable-step (real time) or fixed-step (game time) games.
		/// </summary>
		/// <remarks>
		/// This class is made to resemble the <see cref="Microsoft.Xna.Framework.GameTime"/> class
		/// but with settable properties.
		/// </remarks>
		public class AsyncGameTime
		{
			/// <summary>
			/// Initializes a new instance of the <see cref="AsyncGameTime"/> class.
			/// </summary>
			internal AsyncGameTime() { }

			/// <summary>
			/// The amount of elapsed game time since the last update.
			/// </summary>
			public TimeSpan ElapsedGameTime { get; internal set; }

			/// <summary>
			/// The amount of elapsed real time (wall clock) since the last frame.
			/// </summary>
			public TimeSpan ElapsedRealTime { get; internal set; }

			/// <summary>
			/// Gets a value indicating that the game loop is taking longer than its <see cref="Microsoft.Xna.Framework.Game.TargetElapsedTime"/>.
			/// In this case, the game loop can be considered to be running too slowly and
			/// should do something to "catch up."
			/// </summary>
			public bool IsRunningSlowly { get; internal set; }

			/// <summary>
			/// The amount of game time since the start of the game.
			/// </summary>
			public TimeSpan TotalGameTime { get; internal set; }

			/// <summary>
			/// The amount of real time (wall clock) since the start of the game.
			/// </summary>
			public TimeSpan TotalRealTime { get; internal set; }
		}

		#endregion

		#region AsyncUpdateCallbackCollection Declaration

		/// <summary>
		/// A collection of <see cref="IAsyncUpdateCallback"/> instances.
		/// </summary>
		public class AsyncUpdateCallbackCollection : Collection<IAsyncUpdateCallback>
		{
			/// <summary>
			/// Initializes a new instance of the <see cref="AsyncGameComponent.AsyncUpdateCallbackCollection"/> class.
			/// </summary>
			internal AsyncUpdateCallbackCollection() { }

			#region Collection<IAsyncUpdateCallback> Members

			protected override void InsertItem(int index, IAsyncUpdateCallback item)
			{
				// Make sure the callback has not been added yet.
				if (this.Contains(item))
				{
					throw new ArgumentException("item");
				}

				// Call inherited method.
				base.InsertItem(index, item);
			}

			protected override void SetItem(int index, IAsyncUpdateCallback item)
			{
				throw new NotSupportedException();
			}

			#endregion
		}

		#endregion

		private bool asyncEnabled = true;
#if XBOX
		private int processorAffinity = 4;
		private volatile bool exit;
#elif USE_VOLATILE
		private volatile bool exit;
#else
		private ManualResetEvent exitEvent = new ManualResetEvent(false);
		private WaitHandle[] beginUpdateEvents;
		private WaitHandle[] threadEvents;
#endif
		private AsyncUpdateCallbackCollection callbacks = new AsyncUpdateCallbackCollection();

		private AutoResetEvent beginEvent = new AutoResetEvent(false);
		private ManualResetEvent completedEvent = new ManualResetEvent(true);

		private Thread thread;
		private AsyncGameTime gameTime = new AsyncGameTime();

		private bool internalEnabled;
		private bool initialized;

		private bool updated;
		
		/// <summary>
		/// Initializes a new instance of the <see cref="AsyncGameComponent"/> class.
		/// </summary>
		/// <param name="game">A <see cref="Microsoft.Xna.Framework.Game"/> that the game component should be attached to.</param>
		public AsyncGameComponent(Game game) : base(game)
		{
#if !XBOX && !USE_VOLATILE
			this.beginUpdateEvents = new WaitHandle[] { this.exitEvent, this.completedEvent };
			this.threadEvents = new WaitHandle[] { this.beginEvent, this.exitEvent };
#endif
			// Make sure this component does not get synchronously updated.
			this.internalEnabled = this.Enabled;
			this.Enabled = false;
		}
#if XBOX
		/// <summary>
		/// Gets or set the processor affinity of the running thread.
		/// </summary>
		public int ProcessorAffinity
		{
			get { return this.ProcessorAffinity; }
			set { this.ProcessorAffinity = value; }
		}
#endif

		/// <summary>
		/// Gets the collection of <see cref="IAsyncUpdateCallback"/> instances registered with the <see cref="AsyncGameComponent"/>.
		/// </summary>
		public AsyncUpdateCallbackCollection Callbacks
		{
			get { return this.callbacks; }
		}

		#region GameComponent Members

		protected override void Dispose(bool disposing)
		{
			// Notify the other thread to abort.
			this.ThreadStop();
			
			// Call inherited method.
			base.Dispose(disposing);
		}

		private bool recurse;
		protected override void OnEnabledChanged(object sender, EventArgs args)
		{
			// Prevent entering this method again when seting the enabled property below.
			if (this.recurse)
			{
				return;
			}

			// Hold onto the enabled state.
			this.internalEnabled = this.Enabled;

			// Disable synchronouse updating when asynchronous updating is enabled.
			if (this.asyncEnabled)
			{
				this.recurse = true;
				try
				{
					this.Enabled = false;
				}
				finally
				{
					this.recurse = false;
				}
			}

			// Call inherited method.
			base.OnEnabledChanged(sender, args);
		}

		#endregion

		#region IUpdateable

		public sealed override void Update(GameTime gameTime)
		{
			if (!this.asyncEnabled)
			{
				// Hold onto the game time.
				this.gameTime.TotalRealTime = gameTime.TotalRealTime;
				this.gameTime.ElapsedRealTime = gameTime.ElapsedRealTime;
				this.gameTime.TotalGameTime = gameTime.TotalGameTime;
				this.gameTime.ElapsedGameTime = gameTime.ElapsedGameTime;
				this.gameTime.IsRunningSlowly = gameTime.IsRunningSlowly;

				// Notify subscribers that the update is started.
				for (int i = 0; i < this.callbacks.Count; i++)
				{
					this.callbacks[i].OnUpdateStarted(this.gameTime);
				}

				// Call overloaded method.
				this.Update(this.gameTime);

				// Notify subscribers that the update is completed.
				for (int i = 0; i < this.callbacks.Count; i++)
				{
					this.callbacks[i].OnUpdateCompleted(this.gameTime);
				}
			}
		}

		#endregion

		/// <summary>
		/// An overloaded method that allows asynchronous updating.
		/// </summary>
		/// <param name="gameTime">Snapshot of the game's timing state.</param>
		public virtual void Update(AsyncGameTime gameTime) { }

		#region IAsyncUpdateable Members

		public event EventHandler AsyncEnabledChanged;
		
		public bool AsyncEnabled
		{
			get { return this.asyncEnabled; }
			set
			{
				if (this.asyncEnabled != value)
				{
					this.asyncEnabled = value;

				    if (this.asyncEnabled)
					{
						// Disable synchronized processing.
						this.recurse = true;
						try
						{
							this.Enabled = false;
						}
						finally
						{
							this.recurse = false;
						}

						// Start up the asynchronous processing.
						if (this.initialized)
						{
							this.ThreadStart();
						}
					}
					else
					{
						// Stop the asynchronous processing
						this.ThreadStop();
						this.Enabled = this.internalEnabled;
					}

					// Notify subscribers of changes.
	                this.OnAsyncEnabledChanged(EventArgs.Empty);
				}
			}
		}

		public virtual void BeginUpdate(GameTime gameTime)
		{
			if (this.asyncEnabled)
			{
				// Wait until signaled that a previous update has completed or if the thread has/will exit.
#if XBOX || USE_VOLATILE
				if (this.completedEvent.WaitOne() && !this.exit)
#else
				if (WaitHandle.WaitAny(this.beginUpdateEvents) != 0)
#endif
				{
					// Not completed anymore.
					this.completedEvent.Reset();

					// Hold onto the game time for other thread.
					this.gameTime.TotalRealTime = gameTime.TotalRealTime;
					this.gameTime.ElapsedRealTime = gameTime.ElapsedRealTime;
					this.gameTime.TotalGameTime = gameTime.TotalGameTime;
					this.gameTime.ElapsedGameTime = gameTime.ElapsedGameTime;
					this.gameTime.IsRunningSlowly = gameTime.IsRunningSlowly;

					// Notify subscribers that the update is started.
					for (int i = 0; i < this.callbacks.Count; i++)
					{
						this.callbacks[i].OnUpdateStarted(this.gameTime);
					}

					// Signal the other thread to process.
					this.beginEvent.Set();
				}
			}
		}

		public virtual void EndUpdate()
		{
			if (this.asyncEnabled)
			{
				// Wait until the update is complete.  This won't block if an update was never started.
				this.completedEvent.WaitOne();

				// Notify subscribers that the update is completed.
				if (this.updated)
				{
					for (int i = 0; i < this.callbacks.Count; i++)
					{
						this.callbacks[i].OnUpdateCompleted(this.gameTime);
					}
					this.updated = false;
				}
			}
		}

		#endregion

		#region IGameComponent Members

		public override void Initialize()
		{
			// Call inherited method.
			base.Initialize();

			if (!this.initialized)
			{
				if (this.asyncEnabled)
				{
			        this.ThreadStart();
				}
	            else
				{
		            this.ThreadStop();
				}

				this.initialized = true;
			}
		}

		#endregion

		/// <summary>
		/// Initializes and starts the thread for asynchronous processing.
		/// </summary>
		private void ThreadStart()
		{
			// Reset the the exit event.
#if XBOX || USE_VOLATILE
			this.exit = false;
#else
			this.exitEvent.Reset();
#endif
			// Start the thread.
			this.thread = new Thread(this.ThreadRun);
			this.thread.Start();
		}

		/// <summary>
		/// Stops the the thread for asynchronous processing.
		/// </summary>
		private void ThreadStop()
		{
			// Signal the exit event.
#if XBOX || USE_VOLATILE
			this.exit = true;
			this.beginEvent.Set();
#else
			this.exitEvent.Set();
#endif
			// Wait for the thread to abort.
			if (this.thread != null)
			{
				this.thread.Join();
			}
		}

		/// <summary>
		/// The method thats invoked when the thread start.
		/// </summary>
		private void ThreadRun()
		{
#if XBOX
			// Set the processor affinity.
			Thread.CurrentThread.SetProcessorAffinity(this.processorAffinity);

			// Wait until signaled to start updating or to exit.  An exit event will terminate the thread.
			while (this.beginEvent.WaitOne() && !this.exit)
#elif USE_VOLATILE
			// Wait until signaled to start updating or to exit.  An exit event will terminate the thread.
			while (this.beginEvent.WaitOne() && !this.exit)
#else
			// Wait until signaled to start updating or to exit.  An exit event will terminate the thread.
			// TODO : GC!
			while (WaitHandle.WaitAny(this.threadEvents) != 1)
#endif
			{
				// Update this component.
				this.Update(this.gameTime);
				this.updated = true;

				// Signal completion.
				this.completedEvent.Set();
			}
		}

		/// <summary>
		/// Raises the <see cref="AsyncGameComponent.AsyncEnabledChanged"/> event. 
		/// </summary>
		/// <param name="e">An <see cref="System.EventArgs"/> that contains the event data.</param>
		protected virtual void OnAsyncEnabledChanged(EventArgs e)
		{
			// Notify subscribers of asynchronous activity change.
			if (this.AsyncEnabledChanged != null)
			{
				this.AsyncEnabledChanged(this, e);
			}
		}
	}

	/// <summary>
	/// Calls the <see cref="IAsyncUpdateable.BeginUpdate(GameTime)"/> method.
	/// </summary>
	/// <remarks>
	/// Allows for beginning an asynchronous game component between other
	/// game components.
	/// </remarks>
	public sealed class BeginAsyncGameComponent : GameComponent
	{
		private IAsyncUpdateable asyncUpdateable;

		/// <summary>
		/// /// Initializes a new instance of the <see cref="BeginAsyncGameComponent"/> class.
		/// </summary>
		/// <param name="game">A <see cref="Microsoft.Xna.Framework.Game"/> that the game component should be attached to.</param>
		/// <param name="asyncUpdateable">The <see cref="IAsyncUpdateable"/> to begin asynchronous updating on.</param>
		public BeginAsyncGameComponent(Game game, IAsyncUpdateable asyncUpdateable)	: base(game)
		{
			// The async updateable parameter must be specified.
			if (asyncUpdateable == null)
			{
				throw new ArgumentNullException("asyncUpdateable");
			}

			// Hold onto parameters.
			this.asyncUpdateable = asyncUpdateable;
		}

		#region IUpdateable Members

		public override void Update(GameTime gameTime)
		{
			// Start the async update process.
			this.asyncUpdateable.BeginUpdate(gameTime);
			
			// Call inherited method.
			base.Update(gameTime);
		}

		#endregion
	}

	/// <summary>
	/// Calls the <see cref="IAsyncUpdateable.EndUpdate"/> method.
	/// </summary>
	/// <remarks>
	/// Allows for ending an asynchronous game component's process between
	/// other game components.
	/// </remarks>
	public sealed class EndAsyncGameComponent : GameComponent
	{
		private IAsyncUpdateable asyncUpdateable;

		/// <summary>
		/// Initializes a new instance of the <see cref="EndAsyncGameComponent"/> class.
		/// </summary>
		/// <param name="game">A <see cref="Microsoft.Xna.Framework.Game"/> that the game component should be attached to.</param>
		/// <param name="asyncUpdateable">The <see cref="IAsyncUpdateable"/> to begin asynchronous updating on.</param>
		public EndAsyncGameComponent(Game game, IAsyncUpdateable asyncUpdateable) : base(game)
		{
			// The async updateable parameter must be specified.
			if (asyncUpdateable == null)
			{
				throw new ArgumentNullException("asyncUpdateable");
			}

			// Hold onto parameters.
			this.asyncUpdateable = asyncUpdateable;
		}

		#region IUpdateable Members

		public override void Update(GameTime gameTime)
		{
			// Start the async update process.
			this.asyncUpdateable.EndUpdate();
			
			// Call inherited method.
			base.Update(gameTime);
		}

		#endregion
	}
}
